{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b9ca3aa8-3150-4f2c-88fe-c8d1211ab462",
   "metadata": {},
   "source": [
    "# Chiron - A Feynman-Enhanced Learning Agent Using LangGraph\n",
    "\n",
    "## Overview\n",
    "This notebook presents a structured learning agent implemented using LangGraph. The system guides learners through a sequence of defined but customizable checkpoints, verifying understanding at each step and providing Feynman-style teaching when needed.\n",
    "\n",
    "## Motivation\n",
    "In traditional educational settings, access to personalized 1:1 tutoring is often limited by cost and availability. This project aims to democratize personalized learning by creating an AI tutor that can:\n",
    "- Provide individualized attention and feedback 24/7\n",
    "- Use your own notes and web-retrieved content as context\n",
    "- Offer patient, simple explanations of complex topics\n",
    "\n",
    "## Key Components\n",
    "1. **Learning State Graph**: Orchestrates the sequential learning workflow\n",
    "2. **Checkpoint System**: Defines structured learning milestones\n",
    "3. **Web Search Integration**: Dynamically retrieves relevant learning materials\n",
    "4. **Context Processing**: Chunks and processes learning materials\n",
    "5. **Question Generation**: Creates checkpoint-specific verification questions\n",
    "6. **Understanding Verification**: Evaluates learner comprehension with a clear threshold (70%)\n",
    "7. **Feynman Teaching**: Simplifies complex concepts when understanding is insufficient\n",
    "\n",
    "## Method\n",
    "The system follows a structured learning cycle:\n",
    "\n",
    "### 1. Checkpoint Definition\n",
    "- Generates sequential learning milestones with clear success criteria\n",
    "\n",
    "### 2. Context Building\n",
    "- Processes student-provided materials or retrieves relevant web content\n",
    "\n",
    "### 3. Context Validation\n",
    "- Validates context based on checkpoint criteria\n",
    "- Performs additional web searches if context doesn't meet checkpoint requirements\n",
    "\n",
    "### 4. Embedding Storage\n",
    "- Stores embeddings for retrieving only relevant chunks during verification\n",
    "\n",
    "### 5. Understanding Verification\n",
    "- Generates checkpoint-specific questions\n",
    "- Evaluates responses against a 70% understanding threshold\n",
    "- Provides detailed feedback\n",
    "\n",
    "### 6. Progressive Learning\n",
    "- Advances to the next checkpoint when understanding is verified\n",
    "- Provides Feynman-style explanations when needed\n",
    "\n",
    "## Conclusion\n",
    "This notebook demonstrates a structured approach to guided learning. By combining sequential checkpoints, clear verification thresholds, and Feynman-style teaching, it offers a methodical learning experience with immediate support when needed. The system is particularly effective for complex topics that benefit from step-by-step guidance and simplified explanations.\n",
    "\n",
    "![Chiron](../images/chiron.svg)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bd42e1e-fd75-4719-8590-fd912560482f",
   "metadata": {},
   "source": [
    "## Requirements"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "fcfe0224-298f-480f-93b4-b74a1058f34b",
   "metadata": {},
   "outputs": [],
   "source": [
    "#!pip install langchain-community langchain-openai langgraph pydantic python-dotenv semantic-chunkers semantic-router tavily-python"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac466075-3839-4c1b-b135-12edd7c8b3b7",
   "metadata": {},
   "source": [
    "# Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "eea06412-9aca-45ea-b901-ed8a34619920",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import operator\n",
    "import uuid\n",
    "from typing import Annotated, Dict, List, Optional, Tuple, TypedDict\n",
    "\n",
    "from IPython.display import Image, display\n",
    "from langchain_community.utils.math import cosine_similarity\n",
    "from langchain_community.tools.tavily_search import TavilySearchResults\n",
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.store.memory import InMemoryStore\n",
    "from langgraph.graph import END, START, StateGraph\n",
    "from pydantic import BaseModel, Field\n",
    "from dotenv import load_dotenv\n",
    "from semantic_chunkers import StatisticalChunker\n",
    "from semantic_router.encoders import OpenAIEncoder"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d6d6d45-c4ad-4135-a437-37124997668d",
   "metadata": {},
   "source": [
    "# Setup\n",
    "This agent is implemented using OpenAI's models, but can be used also with self-hosted LLM and embedding models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9e4327f7-05dc-4be0-a7e0-ac7eb47bbfa4",
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv()\n",
    "OpenAI\n",
    "os.environ[\"OPENAI_API_KEY\"] = os.getenv('OPENAI_API_KEY')\n",
    "#TavilySearch\n",
    "os.environ[\"TAVILY_API_KEY\"] = os.getenv('TAVILY_API_KEY')\n",
    "\n",
    "tavily_search = TavilySearchResults(max_results=3)\n",
    "llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
    "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-large\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "559c60be-7291-418d-b2db-ecef5f429d3d",
   "metadata": {},
   "source": [
    "## Data Models Definition\n",
    "In this section, we define the core data structures for our adaptive learning system using Pydantic models. These models ensure type safety and provide clear structure for:\n",
    "- Learning goals and objectives\n",
    "- Checkpoint definitions and tracking\n",
    "- Search queries for dynamic content\n",
    "- Verification of learning progress\n",
    "- Feynman teaching output format\n",
    "- Question generation\n",
    "\n",
    "Each model is designed to capture specific aspects of the learning process while maintaining strict type validation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0ecf74ec-9aca-4023-ac71-c095bf68f194",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Goals(BaseModel):\n",
    "    \"\"\"Structure for defining learning goals\"\"\"\n",
    "    goals: str = Field(None, description=\"Learning goals\")\n",
    "\n",
    "class LearningCheckpoint(BaseModel):\n",
    "    \"\"\"Structure for a single checkpoint\"\"\"\n",
    "    description: str = Field(..., description=\"Main checkpoint description\")\n",
    "    criteria: List[str] = Field(..., description=\"List of success criteria\")\n",
    "    verification: str = Field(..., description=\"How to verify this checkpoint\")\n",
    "\n",
    "class Checkpoints(BaseModel):\n",
    "    \"\"\"Main checkpoints container with index tracking\"\"\"\n",
    "    checkpoints: List[LearningCheckpoint] = Field(\n",
    "        ..., \n",
    "        description=\"List of checkpoints covering foundation, application, and mastery levels\"\n",
    "    )\n",
    "\n",
    "class SearchQuery(BaseModel):\n",
    "    \"\"\"Structure for search query collection\"\"\"\n",
    "    search_queries: list = Field(None, description=\"Search queries for retrieval.\")\n",
    "\n",
    "class LearningVerification(BaseModel):\n",
    "    \"\"\"Structure for verification results\"\"\"\n",
    "    understanding_level: float = Field(..., ge=0, le=1)\n",
    "    feedback: str\n",
    "    suggestions: List[str]\n",
    "    context_alignment: bool\n",
    "\n",
    "class FeynmanTeaching(BaseModel):\n",
    "    \"\"\"Structure for Feynman teaching method\"\"\"\n",
    "    simplified_explanation: str\n",
    "    key_concepts: List[str]\n",
    "    analogies: List[str]\n",
    "\n",
    "class QuestionOutput(BaseModel):\n",
    "    \"\"\"Structure for question generation output\"\"\"\n",
    "    question: str\n",
    "\n",
    "class InContext(BaseModel):\n",
    "    \"\"\"Structure for context verification\"\"\"\n",
    "    is_in_context: str = Field(..., description=\"Yes or No\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "855943ac-3129-4da0-8bb6-7f180044e90c",
   "metadata": {},
   "source": [
    "## Learning State Definition\n",
    "Here we define the main state for our agent. This state tracks:\n",
    "- The learning topic and goals\n",
    "- Context and search results\n",
    "- Current progress through checkpoints\n",
    "- Verification results and teaching outputs\n",
    "- Current question-answer pair"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f4028e38-ca23-48ab-93ed-f490e2ce4a71",
   "metadata": {},
   "outputs": [],
   "source": [
    "class LearningtState(TypedDict):\n",
    "    topic: str\n",
    "    goals: List[Goals]\n",
    "    context: str\n",
    "    context_chunks: Annotated[list, operator.add]\n",
    "    context_key: str\n",
    "    search_queries: SearchQuery\n",
    "    checkpoints: Checkpoints\n",
    "    verifications: LearningVerification\n",
    "    teachings: FeynmanTeaching\n",
    "    current_checkpoint: int\n",
    "    current_question: QuestionOutput\n",
    "    current_answer: str"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f6d49c1-3107-45aa-aa23-8caf9a4effef",
   "metadata": {},
   "source": [
    "## Helper Functions\n",
    "The system uses three utility functions:\n",
    "\n",
    "1. `extract_content_from_chunks`: Processes and combines text chunks into coherent content\n",
    "\n",
    "2. `format_checkpoints_as_message`: Converts checkpoint data into prompt format\n",
    "\n",
    "3. `generate_checkpoint_message`: Creates formatted message for context retrieval"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "4667d407-5f13-4095-93da-dd082b1b4a2b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def extract_content_from_chunks(chunks):\n",
    "    \"\"\"Extract and combine content from chunks with splits attribute.\n",
    "    \n",
    "    Args:\n",
    "        chunks: List of chunk objects that may contain splits attribute\n",
    "        \n",
    "    Returns:\n",
    "        str: Combined content from all chunks joined with newlines\n",
    "    \"\"\"\n",
    "    content = []\n",
    "    \n",
    "    for chunk in chunks:\n",
    "        if hasattr(chunk, 'splits') and chunk.splits:\n",
    "            chunk_content = ' '.join(chunk.splits)\n",
    "            content.append(chunk_content)\n",
    "    \n",
    "    return '\\n'.join(content)\n",
    "\n",
    "def format_checkpoints_as_message(checkpoints: Checkpoints) -> str:\n",
    "    \"\"\"Convert Checkpoints object to a formatted string for the message.\n",
    "    \n",
    "    Args:\n",
    "        checkpoints (Checkpoints): Checkpoints object containing learning checkpoints\n",
    "        \n",
    "    Returns:\n",
    "        str: Formatted string containing numbered checkpoints with descriptions and criteria\n",
    "    \"\"\"\n",
    "    message = \"Here are the learning checkpoints:\\n\\n\"\n",
    "    for i, checkpoint in enumerate(checkpoints.checkpoints, 1):\n",
    "        message += f\"Checkpoint {i}:\\n\"\n",
    "        message += f\"Description: {checkpoint.description}\\n\"\n",
    "        message += \"Success Criteria:\\n\"\n",
    "        for criterion in checkpoint.criteria:\n",
    "            message += f\"- {criterion}\\n\"\n",
    "    return message\n",
    "\n",
    "def generate_checkpoint_message(checks: List[LearningCheckpoint]) -> HumanMessage:\n",
    "    \"\"\"Generate a formatted message for learning checkpoints that need context.\n",
    "    \n",
    "    Args:\n",
    "        checks (List[LearningCheckpoint]): List of learning checkpoint objects\n",
    "        \n",
    "    Returns:\n",
    "        HumanMessage: Formatted message containing checkpoint descriptions, criteria and \n",
    "                     verification methods, ready for context search\n",
    "    \"\"\"\n",
    "    formatted_checks = []\n",
    "    \n",
    "    for check in checks:\n",
    "        checkpoint_text = f\"\"\"\n",
    "        Description: {check.description}\n",
    "        Success Criteria:\n",
    "        {chr(10).join(f'- {criterion}' for criterion in check.criteria)}\n",
    "        Verification Method: {check.verification}\n",
    "        \"\"\"\n",
    "        formatted_checks.append(checkpoint_text)\n",
    "    \n",
    "    all_checks = \"\\n---\\n\".join(formatted_checks)\n",
    "    \n",
    "    checkpoints_message = HumanMessage(content=f\"\"\"The following learning checkpoints need additional context:\n",
    "        {all_checks}\n",
    "        \n",
    "        Please generate search queries to find relevant information.\"\"\")\n",
    "    \n",
    "    return checkpoints_message"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ed58db0-afa2-4591-90f0-51d7027f36e3",
   "metadata": {},
   "source": [
    "## Prompt Configuration\n",
    "Here we define the core instruction prompts for our LLM. Each message serves a specific purpose in the learning process:\n",
    "\n",
    "1. `learning_checkpoints_generator`: Creates structured learning milestones with clear criteria\n",
    "2. `checkpoint_based_query_generator`: Generates targeted search queries for content retrieval\n",
    "3. `question_generator`: Creates verification questions aligned with checkpoints\n",
    "4. `answer_verifier`: Evaluates learner responses against success criteria\n",
    "5. `feynman_teacher`: Crafts simplified explanations using the Feynman technique"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b803f4dd-b580-40de-95f6-a973927c74b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "learning_checkpoints_generator = SystemMessage(content=\"\"\"You will be given a learning topic title and learning objectives.\n",
    "Your goal is to generate clear learning checkpoints that will help verify understanding and progress through the topic.\n",
    "The output should be in the following dictionary structure:\n",
    "checkpoint \n",
    "-> description (level checkpoint description)\n",
    "-> criteria\n",
    "-> verification (How to verify this checkpoint (Feynman Methods))\n",
    "Requirements for each checkpoint:\n",
    "- Description should be clear and concise\n",
    "- Criteria should be specific and measurable (3-5 items)\n",
    "- Verification method should be practical and appropriate for the level\n",
    "- Verification will be checked by language model, so it must by in natural language\n",
    "- All elements should align with the learning objectives\n",
    "- Use action verbs and clear language\n",
    "Ensure all checkpoints progress logically from foundation to mastery.\n",
    "IMPORTANT - ANSWER ONLY 3 CHECKPOINTS\"\"\")\n",
    "\n",
    "checkpoint_based_query_generator = SystemMessage(content=\"\"\"You will be given learning checkpoints for a topic.\n",
    "Your goal is to generate search queries that will retrieve content matching each checkpoint's requirements from retrieval systems or web search.\n",
    "Follow these steps:\n",
    "1. Analyze each learning checkpoint carefully\n",
    "2. For each checkpoint, generate ONE targeted search query that will retrieve:\n",
    "   - Content for checkpoint verification\"\"\")\n",
    "\n",
    "validate_context = SystemMessage(content=\"\"\"You will be given a learning criteria and context.\n",
    "Check if the the criteria could be answered using the context.\n",
    "Always answer YES or NO\"\"\")\n",
    "\n",
    "question_generator = SystemMessage(content=\"\"\"You will be given a checkpoint description, success criteria, and verification method.\n",
    "Your goal is to generate an appropriate question that aligns with the checkpoint's verification requirements.\n",
    "The question should:\n",
    "1. Follow the specified verification method\n",
    "2. Cover all success criteria\n",
    "3. Encourage demonstration of understanding\n",
    "4. Be clear and specific\n",
    "Output should be a single, well-formulated question that effectively tests the checkpoint's learning objectives.\"\"\")\n",
    "\n",
    "answer_verifier = SystemMessage(content=\"\"\"You will be given a student's answer, question, checkpoint details, and relevant context.\n",
    "Your goal is to analyze the answer against the checkpoint criteria and provided context.\n",
    "Analyze considering:\n",
    "1. Alignment with verification method specified\n",
    "2. Coverage of all success criteria\n",
    "3. Use of relevant concepts from context\n",
    "4. Depth and accuracy of understanding\n",
    "Output should include:\n",
    "- understanding_level: float between 0 and 1\n",
    "- feedback: detailed explanation of the assessment\n",
    "- suggestions: list of specific improvements\n",
    "- context_alignment: boolean indicating if the answer aligns with provided context\"\"\")\n",
    "\n",
    "feynman_teacher = SystemMessage(content=\"\"\"You will be given verification results, checkpoint criteria, and learning context.\n",
    "Your goal is to create a Feynman-style teaching explanation for concepts that need reinforcement.\n",
    "The explanation should include:\n",
    "1. Simplified explanation without technical jargon\n",
    "2. Concrete, relatable analogies\n",
    "3. Key concepts to remember\n",
    "Output should follow the Feynman technique:\n",
    "- simplified_explanation: clear, jargon-free explanation\n",
    "- key_concepts: list of essential points\n",
    "- analogies: list of relevant, concrete comparisons\n",
    "Focus on making complex ideas accessible and memorable.\"\"\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "438fbce1-78c8-449a-9494-491399c53a23",
   "metadata": {},
   "source": [
    "## Context Storage\n",
    "The `ContextStore` class manages context chunks and embeddings in memory, optimizing token usage by allowing access to only relevant context during answer verification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "a228f761-da38-4d23-9add-b5178698a6b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ContextStore:\n",
    "    \"\"\"Store for managing context chunks and their embeddings in memory.\n",
    "    \n",
    "    A class that provides storage and retrieval of context data using an in-memory store.\n",
    "    Each context entry consists of context chunks and their corresponding embeddings.\n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(self):\n",
    "        \"\"\"Initialize ContextStore with an empty in-memory store.\"\"\"\n",
    "        self.store = InMemoryStore()\n",
    "        \n",
    "    def save_context(self, context_chunks: list, embeddings: list, key: str = None):\n",
    "        \"\"\"Save context chunks and their embeddings to the store.\n",
    "        \n",
    "        Args:\n",
    "            context_chunks (list): List of context chunk objects\n",
    "            embeddings (list): List of corresponding embeddings for the chunks\n",
    "            key (str, optional): Custom key for storing the context. Defaults to None,\n",
    "                               in which case a UUID is generated.\n",
    "            \n",
    "        Returns:\n",
    "            str: The key used to store the context\n",
    "        \"\"\"\n",
    "        namespace = (\"context\",)\n",
    "        \n",
    "        if key is None:\n",
    "            key = str(uuid.uuid4())\n",
    "            \n",
    "        value = {\n",
    "            \"chunks\": context_chunks,\n",
    "            \"embeddings\": embeddings\n",
    "        }\n",
    "        \n",
    "        self.store.put(namespace, key, value)\n",
    "        return key\n",
    "        \n",
    "    def get_context(self, context_key: str):\n",
    "        \"\"\"Retrieve context data from the store using a key.\n",
    "        \n",
    "        Args:\n",
    "            context_key (str): The key used to store the context\n",
    "            \n",
    "        Returns:\n",
    "            dict: The stored context value containing chunks and embeddings\n",
    "        \"\"\"\n",
    "        namespace = (\"context\",)\n",
    "        memory = self.store.get(namespace, context_key)\n",
    "        return memory.value"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91bab2b6-c2c3-43ec-a705-ac7e97fe7a4a",
   "metadata": {},
   "source": [
    "## Core Learning System Functions\n",
    "The learning system is powered by eight main functions that process and update the `LearningState`:\n",
    "\n",
    "### Content Generation and Processing\n",
    "1. `generate_checkpoints`: Creates learning milestones from topic and goals\n",
    "2. `generate_query`: Formulates checkpoint-based search queries\n",
    "3. `search_web`: Retrieves content via Tavilysearch\n",
    "5. `chunk_context`: Segments learning materials\n",
    "6. `context_validation`: Ensures context meets checkpoint requirements\n",
    "\n",
    "### Learning Verification and Support\n",
    "6. `generate_question`: Creates verification questions\n",
    "7. `verify_answer`: Evaluates against checkpoint criteria\n",
    "8. `teach_concept`: Provides Feynman-style explanations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "9097d06e-cf59-476e-9200-247cadba83aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_query(state: LearningtState):\n",
    "    \"\"\"Generates search queries based on learning checkpoints from current state.\"\"\"\n",
    "    structured_llm = llm.with_structured_output(SearchQuery) \n",
    "    checkpoints_message = HumanMessage(content=format_checkpoints_as_message(state['checkpoints']))  \n",
    "    messages = [checkpoint_based_query_generator, checkpoints_message]\n",
    "    search_queries = structured_llm.invoke(messages)\n",
    "    return {\"search_queries\": search_queries}\n",
    "\n",
    "def search_web(state: LearningtState):\n",
    "    \"\"\"Retrieves and processes web search results based on search queries.\"\"\"\n",
    "    search_queries = state[\"search_queries\"].search_queries\n",
    "    \n",
    "    all_search_docs = []\n",
    "    for query in search_queries:\n",
    "        search_docs = tavily_search.invoke(query)\n",
    "        all_search_docs.extend(search_docs)\n",
    "    \n",
    "    formatted_search_docs = [\n",
    "        f'Context: {doc[\"content\"]}\\n Source: {doc[\"url\"]}\\n'\n",
    "        for doc in all_search_docs\n",
    "    ]\n",
    "\n",
    "    chunk_embeddings = embeddings.embed_documents(formatted_search_docs)\n",
    "    context_key = context_store.save_context(\n",
    "        formatted_search_docs,\n",
    "        chunk_embeddings,\n",
    "        key=state.get('context_key')\n",
    "    )\n",
    "    \n",
    "    return {\"context_chunks\": formatted_search_docs}\n",
    "\n",
    "def generate_checkpoints(state: LearningtState):\n",
    "    \"\"\"Creates learning checkpoints based on given topic and goals.\"\"\"\n",
    "    structured_llm = llm.with_structured_output(Checkpoints)\n",
    "    messages = [\n",
    "        learning_checkpoints_generator,\n",
    "        SystemMessage(content=f\"Topic: {state['topic']}\"),\n",
    "        SystemMessage(content=f\"Goals: {', '.join(str(goal) for goal in state['goals'])}\")\n",
    "    ]\n",
    "    checkpoints = structured_llm.invoke(messages)\n",
    "    return {\"checkpoints\": checkpoints}\n",
    "\n",
    "def chunk_context(state: LearningtState):\n",
    "    \"\"\"Splits context into manageable chunks and generates their embeddings.\"\"\"\n",
    "    encoder = OpenAIEncoder(name=\"text-embedding-3-large\")\n",
    "    chunker = StatisticalChunker(\n",
    "        encoder=encoder,\n",
    "        min_split_tokens=128,\n",
    "        max_split_tokens=512\n",
    "    )\n",
    "    \n",
    "    chunks = chunker([state['context']])\n",
    "    content = []\n",
    "    for chunk in chunks:\n",
    "        content.append(extract_content_from_chunks(chunk))\n",
    "\n",
    "    chunk_embeddings = embeddings.embed_documents(content)\n",
    "    context_key = context_store.save_context(\n",
    "        content,\n",
    "        chunk_embeddings,\n",
    "        key=state.get('context_key')\n",
    "    )\n",
    "    return {\"context_chunks\": content, \"context_key\": context_key}\n",
    "\n",
    "def context_validation(state: LearningtState):\n",
    "    \"\"\"Validates context coverage against checkpoint criteria using stored embeddings.\"\"\"\n",
    "    context = context_store.get_context(state['context_key'])\n",
    "    chunks = context['chunks']\n",
    "    chunk_embeddings = context['embeddings']\n",
    "    \n",
    "    checks = []\n",
    "    structured_llm = llm.with_structured_output(InContext)\n",
    "    \n",
    "    for checkpoint in state['checkpoints'].checkpoints:\n",
    "        query = embeddings.embed_query(checkpoint.verification)\n",
    "        \n",
    "        similarities = cosine_similarity([query], chunk_embeddings)[0]\n",
    "        top_3_indices = sorted(range(len(similarities)), \n",
    "                             key=lambda i: similarities[i], \n",
    "                             reverse=True)[:3]\n",
    "        relevant_chunks = [chunks[i] for i in top_3_indices]\n",
    "        \n",
    "        messages = [\n",
    "            validate_context,\n",
    "            HumanMessage(content=f\"\"\"\n",
    "            Criteria:\n",
    "            {chr(10).join(f\"- {c}\" for c in checkpoint.criteria)}\n",
    "            \n",
    "            Context:\n",
    "            {chr(10).join(relevant_chunks)}\n",
    "            \"\"\")\n",
    "        ]\n",
    "        \n",
    "        response = structured_llm.invoke(messages)\n",
    "        if response.is_in_context.lower() == \"no\":\n",
    "            checks.append(checkpoint)\n",
    "    \n",
    "    if checks:\n",
    "        structured_llm = llm.with_structured_output(SearchQuery)\n",
    "        checkpoints_message = generate_checkpoint_message(checks)\n",
    "        \n",
    "        messages = [checkpoint_based_query_generator, checkpoints_message]\n",
    "        search_queries = structured_llm.invoke(messages)\n",
    "        return {\"search_queries\": search_queries}\n",
    "    \n",
    "    return {\"search_queries\": None}\n",
    "\n",
    "def generate_question(state: LearningtState):\n",
    "    \"\"\"Generates assessment questions based on current checkpoint verification requirements.\"\"\"\n",
    "    structured_llm = llm.with_structured_output(QuestionOutput)\n",
    "    current_checkpoint = state['current_checkpoint']\n",
    "    checkpoint_info = state['checkpoints'].checkpoints[current_checkpoint]\n",
    "    \n",
    "    messages = [\n",
    "        question_generator,\n",
    "        HumanMessage(content=f\"\"\"\n",
    "        Checkpoint Description: {checkpoint_info.description}\n",
    "        Success Criteria:\n",
    "        {chr(10).join(f\"- {c}\" for c in checkpoint_info.criteria)}\n",
    "        Verification Method: {checkpoint_info.verification}\n",
    "        \n",
    "        Generate an appropriate verification question.\"\"\")\n",
    "    ]\n",
    "    \n",
    "    question_output = structured_llm.invoke(messages)\n",
    "    return {\"current_question\": question_output.question}\n",
    "\n",
    "def verify_answer(state: LearningtState):\n",
    "    \"\"\"Evaluates user answers against checkpoint criteria using relevant context chunks.\"\"\"\n",
    "    structured_llm = llm.with_structured_output(LearningVerification)\n",
    "    current_checkpoint = state['current_checkpoint']\n",
    "    checkpoint_info = state['checkpoints'].checkpoints[current_checkpoint]\n",
    "    \n",
    "    context = context_store.get_context(state['context_key'])\n",
    "    chunks = context['chunks']\n",
    "    chunk_embeddings = context['embeddings']\n",
    "    \n",
    "    query = embeddings.embed_query(checkpoint_info.verification)\n",
    "    \n",
    "    similarities = cosine_similarity([query], chunk_embeddings)[0]\n",
    "    top_3_indices = sorted(range(len(similarities)), \n",
    "                         key=lambda i: similarities[i], \n",
    "                         reverse=True)[:3]\n",
    "    relevant_chunks = [chunks[i] for i in top_3_indices]\n",
    "    \n",
    "    messages = [\n",
    "        answer_verifier,\n",
    "        HumanMessage(content=f\"\"\"\n",
    "        Question: {state['current_question']}\n",
    "        Answer: {state['current_answer']}\n",
    "        \n",
    "        Checkpoint Description: {checkpoint_info.description}\n",
    "        Success Criteria:\n",
    "        {chr(10).join(f\"- {c}\" for c in checkpoint_info.criteria)}\n",
    "        Verification Method: {checkpoint_info.verification}\n",
    "        \n",
    "        Context:\n",
    "        {chr(10).join(relevant_chunks)}\n",
    "        \n",
    "        Assess the answer.\"\"\")\n",
    "    ]\n",
    "    \n",
    "    verification = structured_llm.invoke(messages)\n",
    "    return {\"verifications\": verification}\n",
    "    \n",
    "def teach_concept(state: LearningtState):\n",
    "    \"\"\"Creates simplified Feynman-style explanations for concepts that need reinforcement.\"\"\"\n",
    "    structured_llm = llm.with_structured_output(FeynmanTeaching)\n",
    "    current_checkpoint = state['current_checkpoint']\n",
    "    checkpoint_info = state['checkpoints'].checkpoints[current_checkpoint]\n",
    "    \n",
    "    messages = [\n",
    "        feynman_teacher,\n",
    "        HumanMessage(content=f\"\"\"\n",
    "        Criteria: {checkpoint_info.criteria}\n",
    "        Verification: {state['verifications']}\n",
    "        \n",
    "        Context:\n",
    "        {state['context_chunks']}\n",
    "        \n",
    "        Create a Feynman teaching explanation.\"\"\")\n",
    "    ]\n",
    "    \n",
    "    teaching = structured_llm.invoke(messages)\n",
    "    return {\"teachings\": teaching}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0db57b9-281e-465f-97e2-9fd10279d8d3",
   "metadata": {},
   "source": [
    "## Helper State Management Functions\n",
    "Here we define two auxiliary functions that manage the learning flow:\n",
    "\n",
    "1. `user_answer`: Placeholder for collecting user responses to verification questions\n",
    "2. `next_checkpoint`: Increments the checkpoint counter to progress through learning milestones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ddd5e79a-d675-476b-b52e-c8ced9d4ed05",
   "metadata": {},
   "outputs": [],
   "source": [
    "def user_answer(state: LearningtState):\n",
    "    \"\"\"Placeholder for handling user's answer input.\"\"\"\n",
    "    pass\n",
    "\n",
    "def next_checkpoint(state: LearningtState):\n",
    "    \"\"\"Advances to the next checkpoint in the learning sequence.\"\"\"\n",
    "    current_checkpoint = state['current_checkpoint'] + 1\n",
    "    return {'current_checkpoint': current_checkpoint}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf396447-7a62-477c-b7ff-73ceb7cd2457",
   "metadata": {},
   "source": [
    "## Routing Logic Functions\n",
    "Four routing functions control the agent's workflow:\n",
    "\n",
    "1. `route_context`: Manages context processing vs. query generation\n",
    "2. `route_verification`: Directs flow based on understanding level (70% threshold)\n",
    "3. `route_teaching`: Handles post-teaching progression\n",
    "4. `route_search`: Back to search if context is irrelevant"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "98026d70-35c1-4efc-bbe0-08178e0953a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def route_context(state: LearningtState):\n",
    "    \"\"\"Determines whether to process existing context or generate new search queries.\"\"\"\n",
    "    if state.get(\"context\"):\n",
    "        return 'chunk_context'\n",
    "    return 'generate_query'\n",
    "\n",
    "def route_verification(state: LearningtState):\n",
    "    \"\"\"Determines next step based on verification results and checkpoint progress.\"\"\"\n",
    "    current_checkpoint = state['current_checkpoint']\n",
    "    \n",
    "    if state['verifications'].understanding_level < 0.7:\n",
    "        return 'teach_concept'\n",
    "        \n",
    "    if current_checkpoint + 1 < len(state['checkpoints'].checkpoints):\n",
    "        return 'next_checkpoint'\n",
    "    \n",
    "    return END\n",
    "\n",
    "def route_teaching(state: LearningtState):\n",
    "    \"\"\"Routes to next checkpoint or ends session after teaching intervention.\"\"\"\n",
    "    current_checkpoint = state['current_checkpoint']\n",
    "    if current_checkpoint + 1 < len(state['checkpoints'].checkpoints):\n",
    "        return 'next_checkpoint'\n",
    "    return END\n",
    "\n",
    "def route_search(state: LearningtState):\n",
    "    \"\"\"Directs flow between question generation and web search based on query status.\"\"\"\n",
    "    if state['search_queries'] is None:\n",
    "        return \"generate_question\"\n",
    "    return \"search_web\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99e2154b-f06e-45e9-a0d8-fa112623e4ef",
   "metadata": {},
   "source": [
    "## Building the Learning Flow Graph\n",
    "Here we construct the complete graph structure for our adaptive learning system using LangGraph. The graph defines:\n",
    "\n",
    "1. **Node Setup**\n",
    "  - Core processing nodes (generate_query, search_web, chunk_context, context_validation)\n",
    "  - Learning management nodes (generate_checkpoints, generate_question)\n",
    "  - Interactive nodes (user_answer)\n",
    "  - Evaluation nodes (verify_answer, teach_concept)\n",
    "\n",
    "2. **Flow Definition**\n",
    "  - Starting point (generate_checkpoints)\n",
    "  - Conditional paths based on context availability and verification results\n",
    "  - Interactive breaks for user input\n",
    "\n",
    "3. **Graph Configuration**\n",
    "  - Uses MemorySaver for persistence\n",
    "  - Uses ContextStore to keep embeddings\n",
    "  - Includes human in the loop strategy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "5d42bb27-9e0b-4fe6-9bdf-3f2a289ec4da",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "searcher = StateGraph(LearningtState)\n",
    "memory = MemorySaver()\n",
    "context_store = ContextStore()\n",
    "\n",
    "searcher.add_node(\"generate_query\", generate_query)\n",
    "searcher.add_node(\"search_web\", search_web)\n",
    "searcher.add_node(\"chunk_context\", chunk_context)\n",
    "searcher.add_node(\"context_validation\", context_validation)\n",
    "searcher.add_node(\"generate_checkpoints\", generate_checkpoints)\n",
    "searcher.add_node(\"generate_question\", generate_question)\n",
    "searcher.add_node(\"next_checkpoint\", next_checkpoint)\n",
    "searcher.add_node(\"user_answer\", user_answer)\n",
    "searcher.add_node(\"verify_answer\", verify_answer)\n",
    "searcher.add_node(\"teach_concept\", teach_concept)\n",
    "\n",
    "# Flow\n",
    "searcher.add_edge(START, \"generate_checkpoints\")\n",
    "searcher.add_conditional_edges('generate_checkpoints', route_context,['chunk_context', 'generate_query'])\n",
    "searcher.add_edge(\"generate_query\", \"search_web\")\n",
    "searcher.add_edge(\"search_web\", \"generate_question\")\n",
    "searcher.add_edge(\"chunk_context\", 'context_validation')\n",
    "searcher.add_conditional_edges('context_validation', route_search,['search_web', 'generate_question'])\n",
    "\n",
    "searcher.add_edge(\"generate_question\", \"user_answer\")\n",
    "searcher.add_edge(\"user_answer\", \"verify_answer\")\n",
    "searcher.add_conditional_edges(\n",
    "    \"verify_answer\",\n",
    "    route_verification,\n",
    "    {\n",
    "        \"next_checkpoint\": \"next_checkpoint\",\n",
    "        \"teach_concept\": \"teach_concept\",\n",
    "        END: END\n",
    "    }\n",
    ")\n",
    "\n",
    "searcher.add_conditional_edges(\n",
    "    \"teach_concept\",\n",
    "    route_teaching,\n",
    "    {\n",
    "        \"next_checkpoint\": \"next_checkpoint\",\n",
    "        END: END\n",
    "    }\n",
    ")\n",
    "searcher.add_edge(\"next_checkpoint\", \"generate_question\")\n",
    "\n",
    "\n",
    "\n",
    "graph = searcher.compile(interrupt_after=[\"generate_checkpoints\"], interrupt_before=[\"user_answer\"], checkpointer=memory)\n",
    "\n",
    "display(Image(graph.get_graph(xray=1).draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98346036-35c7-497a-bb94-e415b4f6f230",
   "metadata": {},
   "source": [
    "# Agent Use Case - Learn Anemia from own note"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e42dcf9-db91-450e-9c6f-f6f7d120d5ce",
   "metadata": {},
   "source": [
    "## Pretty print helper functions\n",
    "\n",
    "Helper functions to improve output readability and example visibility:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "c12b9dd9-8d43-49c8-9c7e-066c5e71e810",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "def print_checkpoints(event):\n",
    "    \"\"\"Pretty print checkpoints information with improved visual formatting\"\"\"\n",
    "    checkpoints = event.get('checkpoints', '')\n",
    "    if checkpoints:\n",
    "        print(\"\\n\" + \"=\" * 80)\n",
    "        print(\"🎯 LEARNING CHECKPOINTS OVERVIEW\".center(80))\n",
    "        print(\"=\" * 80 + \"\\n\")\n",
    "        \n",
    "        for i, checkpoint in enumerate(checkpoints.checkpoints, 1):\n",
    "            # Checkpoint header with number\n",
    "            print(f\"📍 CHECKPOINT #{i}\".center(80))\n",
    "            print(\"─\" * 80 + \"\\n\")\n",
    "            \n",
    "            # Description section with text wrapping\n",
    "            print(\"📝 Description:\")\n",
    "            print(\"─\" * 40)\n",
    "            words = checkpoint.description.split()\n",
    "            current_line = []\n",
    "            current_length = 0\n",
    "            \n",
    "            for word in words:\n",
    "                if current_length + len(word) + 1 <= 70:\n",
    "                    current_line.append(word)\n",
    "                    current_length += len(word) + 1\n",
    "                else:\n",
    "                    print(f\"  {' '.join(current_line)}\")\n",
    "                    current_line = [word]\n",
    "                    current_length = len(word)\n",
    "            \n",
    "            if current_line:\n",
    "                print(f\"  {' '.join(current_line)}\")\n",
    "            print()\n",
    "            \n",
    "            # Success Criteria section\n",
    "            print(\"✅ Success Criteria:\")\n",
    "            print(\"─\" * 40)\n",
    "            for j, criterion in enumerate(checkpoint.criteria, 1):\n",
    "                # Wrap each criterion text\n",
    "                words = criterion.split()\n",
    "                current_line = []\n",
    "                current_length = 0\n",
    "                first_line = True\n",
    "                \n",
    "                for word in words:\n",
    "                    if current_length + len(word) + 1 <= 66:  # Shorter width to account for numbering\n",
    "                        current_line.append(word)\n",
    "                        current_length += len(word) + 1\n",
    "                    else:\n",
    "                        if first_line:\n",
    "                            print(f\"  {j}. {' '.join(current_line)}\")\n",
    "                            first_line = False\n",
    "                        else:\n",
    "                            print(f\"     {' '.join(current_line)}\")\n",
    "                        current_line = [word]\n",
    "                        current_length = len(word)\n",
    "                \n",
    "                if current_line:\n",
    "                    if first_line:\n",
    "                        print(f\"  {j}. {' '.join(current_line)}\")\n",
    "                    else:\n",
    "                        print(f\"     {' '.join(current_line)}\")\n",
    "            print()\n",
    "            \n",
    "            # Verification Method section\n",
    "            print(\"🔍 Verification Method:\")\n",
    "            print(\"─\" * 40)\n",
    "            words = checkpoint.verification.split()\n",
    "            current_line = []\n",
    "            current_length = 0\n",
    "            \n",
    "            for word in words:\n",
    "                if current_length + len(word) + 1 <= 70:\n",
    "                    current_line.append(word)\n",
    "                    current_length += len(word) + 1\n",
    "                else:\n",
    "                    print(f\"  {' '.join(current_line)}\")\n",
    "                    current_line = [word]\n",
    "                    current_length = len(word)\n",
    "            \n",
    "            if current_line:\n",
    "                print(f\"  {' '.join(current_line)}\")\n",
    "            print()\n",
    "            \n",
    "            # Separator between checkpoints\n",
    "            if i < len(checkpoints.checkpoints):\n",
    "                print(\"~\" * 80 + \"\\n\")\n",
    "        \n",
    "        print(\"=\" * 80 + \"\\n\")\n",
    "\n",
    "def print_verification_results(event):\n",
    "    \"\"\"Pretty print verification results with improved formatting\"\"\"\n",
    "    verifications = event.get('verifications', '')\n",
    "    if verifications:\n",
    "        print(\"\\n\" + \"=\" * 50)\n",
    "        print(\"📊 VERIFICATION RESULTS\".center(50))\n",
    "        print(\"=\" * 50 + \"\\n\")\n",
    "\n",
    "        # Understanding Level with visual bar\n",
    "        understanding = verifications.understanding_level\n",
    "        bar_length = 20\n",
    "        filled_length = int(understanding * bar_length)\n",
    "        bar = \"█\" * filled_length + \"░\" * (bar_length - filled_length)\n",
    "        \n",
    "        print(f\"📈 Understanding Level: [{bar}] {understanding * 100:.1f}%\\n\")\n",
    "        \n",
    "        # Feedback section\n",
    "        print(\"💡 Feedback:\")\n",
    "        print(f\"{verifications.feedback}\\n\")\n",
    "        \n",
    "        # Suggestions section\n",
    "        print(\"🎯 Suggestions:\")\n",
    "        for i, suggestion in enumerate(verifications.suggestions, 1):\n",
    "            print(f\"  {i}. {suggestion}\")\n",
    "        print()\n",
    "        \n",
    "        # Context Alignment\n",
    "        print(\"🔍 Context Alignment:\")\n",
    "        print(f\"{verifications.context_alignment}\\n\")\n",
    "        \n",
    "        print(\"-\" * 50 + \"\\n\")\n",
    "def print_teaching_results(event):\n",
    "    \"\"\"Pretty print Feynman teaching results with improved formatting\"\"\"\n",
    "    teachings = event.get('teachings', '')\n",
    "    if teachings:\n",
    "        print(\"\\n\" + \"=\" * 70)\n",
    "        print(\"🎓 FEYNMAN TEACHING EXPLANATION\".center(70))\n",
    "        print(\"=\" * 70 + \"\\n\")\n",
    "\n",
    "        # Simplified Explanation section\n",
    "        print(\"📚 SIMPLIFIED EXPLANATION:\")\n",
    "        print(\"─\" * 30)\n",
    "        # Split explanation into paragraphs for better readability\n",
    "        paragraphs = teachings.simplified_explanation.split('\\n')\n",
    "        for paragraph in paragraphs:\n",
    "            # Wrap text at 60 characters for better readability\n",
    "            words = paragraph.split()\n",
    "            lines = []\n",
    "            current_line = []\n",
    "            current_length = 0\n",
    "            \n",
    "            for word in words:\n",
    "                if current_length + len(word) + 1 <= 60:\n",
    "                    current_line.append(word)\n",
    "                    current_length += len(word) + 1\n",
    "                else:\n",
    "                    lines.append(' '.join(current_line))\n",
    "                    current_line = [word]\n",
    "                    current_length = len(word)\n",
    "            \n",
    "            if current_line:\n",
    "                lines.append(' '.join(current_line))\n",
    "            \n",
    "            for line in lines:\n",
    "                print(f\"{line}\")\n",
    "            print()\n",
    "        \n",
    "        # Key Concepts section\n",
    "        print(\"💡 KEY CONCEPTS:\")\n",
    "        print(\"─\" * 30)\n",
    "        for i, concept in enumerate(teachings.key_concepts, 1):\n",
    "            print(f\"  {i}. {concept}\")\n",
    "        print()\n",
    "        \n",
    "        # Analogies section\n",
    "        print(\"🔄 ANALOGIES & EXAMPLES:\")\n",
    "        print(\"─\" * 30)\n",
    "        for i, analogy in enumerate(teachings.analogies, 1):\n",
    "            print(f\"  {i}. {analogy}\")\n",
    "        print()\n",
    "        \n",
    "        print(\"=\" * 70 + \"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1644c25e-96d3-4a82-b948-a4d7457afe34",
   "metadata": {},
   "source": [
    "## Example School Note"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "9dd3a5c4-beaf-4111-afc9-f30354115497",
   "metadata": {},
   "outputs": [],
   "source": [
    "note = \"\"\"Anemia: A Comprehensive Overview\n",
    "Definition\n",
    "Anemia is a medical condition characterized by a decrease in the total number of red blood cells (RBCs) or hemoglobin in the blood. This reduction leads to a diminished ability to carry oxygen to the body's tissues, affecting overall body function and health.\n",
    "Blood Components and Their Role\n",
    "Red blood cells, also known as erythrocytes, are fundamental components of blood that carry oxygen throughout the body. These cells contain hemoglobin, an iron-containing protein that gives blood its characteristic red color and is responsible for oxygen transport. The typical lifespan of a red blood cell is approximately 120 days, after which it must be replaced by new cells produced in the bone marrow.\n",
    "Types of Anemia\n",
    "Iron Deficiency Anemia represents the most prevalent form of anemia worldwide. It occurs due to insufficient iron intake or absorption, particularly affecting pregnant women, growing children, menstruating women, and individuals with poor nutritional intake.\n",
    "Vitamin Deficiency Anemia develops when the body lacks sufficient amounts of vitamin B12 or folate (vitamin B9). This deficiency can stem from dietary inadequacies or problems with nutrient absorption in the digestive system.\n",
    "Aplastic Anemia, though rare, presents a serious condition where the bone marrow fails to produce adequate blood cells. This form can be either inherited through genetic factors or acquired through various environmental causes or medical conditions.\n",
    "Hemolytic Anemia occurs when red blood cells are destroyed at a rate faster than the body can replace them. This condition may be inherited through genetic factors or acquired through various external causes.\n",
    "Clinical Manifestations\n",
    "Anemia manifests through various symptoms including persistent fatigue and weakness. Patients often present with pale or yellowish skin, experience shortness of breath, and may suffer from dizziness. Additional symptoms include irregular heartbeat patterns, frequent headaches, cold extremities, and occasional chest pain.\n",
    "Diagnostic Approach\n",
    "Diagnosis begins with a thorough physical examination by a healthcare provider. Blood tests form the cornerstone of diagnosis, including a Complete Blood Count (CBC), assessment of iron levels, vitamin B12 measurement, and folate level determination. These tests help identify the specific type of anemia and guide appropriate treatment.\n",
    "Treatment Strategies\n",
    "Dietary modification serves as a fundamental treatment approach. This involves increasing consumption of iron-rich foods such as red meat, dark leafy vegetables, legumes, and iron-fortified cereals.\n",
    "Supplementation often proves necessary and may include iron supplements, vitamin B12, or folic acid, depending on the underlying cause of anemia.\n",
    "Medical interventions become necessary in severe cases. Blood transfusions may be required for severe anemia, while bone marrow transplantation might be considered for cases of aplastic anemia.\n",
    "Preventive Measures\n",
    "Prevention centers on maintaining a balanced diet rich in essential nutrients, particularly iron, vitamin B12, folate, and vitamin C, which enhances iron absorption. Regular medical check-ups allow for early detection and intervention.\n",
    "Certain populations require special attention regarding prevention. These include pregnant women, menstruating women, growing children, individuals following vegetarian or vegan diets, and athletes who may have increased nutritional demands.\n",
    "Potential Complications\n",
    "Untreated anemia can lead to several serious complications. These include severe fatigue that impacts daily activities, complications during pregnancy, cardiovascular problems, depression, and cognitive difficulties that may affect work or school performance.\n",
    "Clinical Significance\n",
    "Anemia often serves as an indicator of other underlying medical conditions. Therefore, early detection and appropriate treatment prove crucial for optimal outcomes. Different forms of anemia require specific treatment approaches, and regular monitoring may be necessary to ensure treatment effectiveness.\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "611d6d38-d036-48c3-a819-442e65a07a23",
   "metadata": {},
   "source": [
    "## Initial state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "08dec5ad-f0bf-493f-8e72-fe0409b5c9db",
   "metadata": {},
   "outputs": [],
   "source": [
    "initial_input = {\n",
    "    \"topic\": \"Anemia\",\n",
    "    'goals': ['Im medical student, i want to master the diagnosis of Anemia'],\n",
    "    'context': note,\n",
    "    'current_checkpoint': 0}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a2dfe81-c2da-47cc-b209-a0fbe611b493",
   "metadata": {},
   "source": [
    "## Generate learning checkpoints"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "da6ab6f4-0fae-48a4-8c80-620d5c9e3a3a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "================================================================================\n",
      "                        🎯 LEARNING CHECKPOINTS OVERVIEW                         \n",
      "================================================================================\n",
      "\n",
      "                                📍 CHECKPOINT #1                                 \n",
      "────────────────────────────────────────────────────────────────────────────────\n",
      "\n",
      "📝 Description:\n",
      "────────────────────────────────────────\n",
      "  Understand the basic concepts and types of anemia.\n",
      "\n",
      "✅ Success Criteria:\n",
      "────────────────────────────────────────\n",
      "  1. Define anemia and its general causes.\n",
      "  2. Identify the major types of anemia (e.g., iron deficiency,\n",
      "     vitamin B12 deficiency, hemolytic anemia).\n",
      "  3. Explain the physiological impact of anemia on the body.\n",
      "\n",
      "🔍 Verification Method:\n",
      "────────────────────────────────────────\n",
      "  Explain anemia in simple terms to a peer, including its definition,\n",
      "  causes, and types. Use examples to illustrate the physiological\n",
      "  effects of anemia on the body.\n",
      "\n",
      "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
      "\n",
      "                                📍 CHECKPOINT #2                                 \n",
      "────────────────────────────────────────────────────────────────────────────────\n",
      "\n",
      "📝 Description:\n",
      "────────────────────────────────────────\n",
      "  Apply diagnostic criteria to identify different types of anemia.\n",
      "\n",
      "✅ Success Criteria:\n",
      "────────────────────────────────────────\n",
      "  1. List the common symptoms and signs associated with each type of\n",
      "     anemia.\n",
      "  2. Interpret laboratory tests (e.g., CBC, reticulocyte count, iron\n",
      "     studies) to differentiate between types of anemia.\n",
      "  3. Describe the role of patient history and physical examination in\n",
      "     diagnosing anemia.\n",
      "\n",
      "🔍 Verification Method:\n",
      "────────────────────────────────────────\n",
      "  Present a case study to a peer, detailing the symptoms, lab results,\n",
      "  and diagnosis process for a specific type of anemia. Discuss how you\n",
      "  used diagnostic criteria to reach your conclusion.\n",
      "\n",
      "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
      "\n",
      "                                📍 CHECKPOINT #3                                 \n",
      "────────────────────────────────────────────────────────────────────────────────\n",
      "\n",
      "📝 Description:\n",
      "────────────────────────────────────────\n",
      "  Master the comprehensive approach to diagnosing anemia in clinical\n",
      "  practice.\n",
      "\n",
      "✅ Success Criteria:\n",
      "────────────────────────────────────────\n",
      "  1. Develop a systematic approach to evaluating a patient with\n",
      "     suspected anemia.\n",
      "  2. Integrate laboratory findings with clinical presentation to form\n",
      "     a differential diagnosis.\n",
      "  3. Formulate a management plan based on the type and severity of\n",
      "     anemia.\n",
      "\n",
      "🔍 Verification Method:\n",
      "────────────────────────────────────────\n",
      "  Conduct a mock patient interview and examination with a peer,\n",
      "  followed by a discussion on the differential diagnosis and management\n",
      "  plan. Reflect on the process and identify areas for improvement.\n",
      "\n",
      "================================================================================\n",
      "\n"
     ]
    }
   ],
   "source": [
    "thread = {\"configurable\": {\"thread_id\": \"20\"}}\n",
    "\n",
    "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
    "    print_checkpoints(event)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f81b3852-7d5e-4583-af32-21bdd77ea727",
   "metadata": {},
   "source": [
    "## Widget for checkpoin management"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "b545f08e-2341-49fc-aae6-c49cb983aea5",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "from typing import List\n",
    "import ipywidgets as widgets\n",
    "from IPython.display import display\n",
    "from pydantic import BaseModel\n",
    "\n",
    "def create_checkpoint_editor(checkpoints_model: Checkpoints):\n",
    "    \"\"\"\n",
    "    Creates an interactive checkpoint editor using a Pydantic model.\n",
    "    \n",
    "    Args:\n",
    "        checkpoints_model: Pydantic model of Checkpoints class\n",
    "    \"\"\"\n",
    "    # Convert to list of dictionaries for easier editing\n",
    "    checkpoints = [cp.model_dump() for cp in checkpoints_model.checkpoints]\n",
    "    checkpoints_widgets = []\n",
    "    accepted_checkpoints = []\n",
    "    \n",
    "    def create_criterion_widget(checkpoint_index: int, criterion_value: str = \"\", criterion_index: int = None):\n",
    "        \"\"\"Creates a widget for a single criterion with a delete button\"\"\"\n",
    "        criterion_container = widgets.HBox([\n",
    "            widgets.Text(\n",
    "                value=criterion_value,\n",
    "                description=f'{criterion_index + 1}.' if criterion_index is not None else 'New',\n",
    "                layout=widgets.Layout(width='85%')\n",
    "            ),\n",
    "            widgets.Button(\n",
    "                description='Delete',\n",
    "                button_style='danger',\n",
    "                layout=widgets.Layout(width='15%')\n",
    "            )\n",
    "        ])\n",
    "        \n",
    "        def on_criterion_change(change):\n",
    "            nonlocal criterion_index\n",
    "            if criterion_index is not None:\n",
    "                checkpoints[checkpoint_index]['criteria'][criterion_index] = change['new']\n",
    "        \n",
    "        def remove_criterion(b):\n",
    "            if criterion_index is not None:\n",
    "                checkpoints[checkpoint_index]['criteria'].pop(criterion_index)\n",
    "                update_checkpoint_widget(checkpoint_index)\n",
    "        \n",
    "        criterion_container.children[0].observe(on_criterion_change, names='value')\n",
    "        criterion_container.children[1].on_click(remove_criterion)\n",
    "        \n",
    "        return criterion_container\n",
    "    \n",
    "    def create_checkpoint_widget(checkpoint: dict, index: int):\n",
    "        \"\"\"Creates a widget for a single checkpoint\"\"\"\n",
    "        \n",
    "        def on_accept_change(change):\n",
    "            if change['new']:\n",
    "                accepted_checkpoints.append(index)\n",
    "            else:\n",
    "                if index in accepted_checkpoints:\n",
    "                    accepted_checkpoints.remove(index)\n",
    "        \n",
    "        def on_description_change(change):\n",
    "            checkpoints[index]['description'] = change['new']\n",
    "        \n",
    "        def on_verification_change(change):\n",
    "            checkpoints[index]['verification'] = change['new']\n",
    "        \n",
    "        def add_criterion(b):\n",
    "            checkpoints[index]['criteria'].append(\"\")\n",
    "            update_checkpoint_widget(index)\n",
    "        \n",
    "        def remove_checkpoint(b):\n",
    "            checkpoints.pop(index)\n",
    "            update_all_checkpoints()\n",
    "        \n",
    "        # Header with checkbox and delete button\n",
    "        header = widgets.HBox([\n",
    "            widgets.HTML(f'<h3 style=\"margin: 0;\">Checkpoint {index + 1}</h3>'),\n",
    "            widgets.Checkbox(\n",
    "                value=False,\n",
    "                description='Accept',\n",
    "                indent=False,\n",
    "                layout=widgets.Layout(margin='5px 0 0 20px')\n",
    "            ),\n",
    "            widgets.Button(\n",
    "                description='Delete checkpoint',\n",
    "                button_style='danger',\n",
    "                layout=widgets.Layout(margin='0 0 0 20px')\n",
    "            )\n",
    "        ])\n",
    "        \n",
    "        # Description\n",
    "        description = widgets.Textarea(\n",
    "            value=checkpoint['description'],\n",
    "            description='Description:',\n",
    "            layout=widgets.Layout(width='95%', height='60px')\n",
    "        )\n",
    "        \n",
    "        # Criteria\n",
    "        criteria_label = widgets.HTML('<b>Criteria:</b>')\n",
    "        criteria_container = widgets.VBox([\n",
    "            create_criterion_widget(index, criterion, i)\n",
    "            for i, criterion in enumerate(checkpoint['criteria'])\n",
    "        ])\n",
    "        \n",
    "        # Add criterion button\n",
    "        add_criterion_btn = widgets.Button(\n",
    "            description='Add criterion',\n",
    "            button_style='success',\n",
    "            layout=widgets.Layout(margin='10px 0')\n",
    "        )\n",
    "        \n",
    "        # Verification\n",
    "        verification = widgets.Textarea(\n",
    "            value=checkpoint['verification'],\n",
    "            description='Verification:',\n",
    "            layout=widgets.Layout(width='95%', height='60px', margin='10px 0')\n",
    "        )\n",
    "        \n",
    "        separator = widgets.HTML('<hr style=\"margin: 20px 0;\">')\n",
    "        \n",
    "        # Combine all elements\n",
    "        checkpoint_widget = widgets.VBox([\n",
    "            header,\n",
    "            description,\n",
    "            criteria_label,\n",
    "            criteria_container,\n",
    "            add_criterion_btn,\n",
    "            verification,\n",
    "            separator\n",
    "        ])\n",
    "        \n",
    "        # Add observers and handlers\n",
    "        header.children[1].observe(on_accept_change, names='value')\n",
    "        header.children[2].on_click(remove_checkpoint)\n",
    "        description.observe(on_description_change, names='value')\n",
    "        verification.observe(on_verification_change, names='value')\n",
    "        add_criterion_btn.on_click(add_criterion)\n",
    "        \n",
    "        return checkpoint_widget\n",
    "    \n",
    "    def update_checkpoint_widget(index: int):\n",
    "        \"\"\"Updates a single checkpoint widget\"\"\"\n",
    "        if 0 <= index < len(checkpoints):\n",
    "            checkpoints_widgets[index] = create_checkpoint_widget(checkpoints[index], index)\n",
    "            update_main_container()\n",
    "    \n",
    "    def update_all_checkpoints():\n",
    "        \"\"\"Updates all checkpoint widgets\"\"\"\n",
    "        nonlocal checkpoints_widgets\n",
    "        checkpoints_widgets = [\n",
    "            create_checkpoint_widget(checkpoint, i)\n",
    "            for i, checkpoint in enumerate(checkpoints)\n",
    "        ]\n",
    "        update_main_container()\n",
    "    \n",
    "    def add_new_checkpoint(b):\n",
    "        \"\"\"Adds a new checkpoint\"\"\"\n",
    "        checkpoints.append({\n",
    "            'description': '',\n",
    "            'criteria': [],\n",
    "            'verification': ''\n",
    "        })\n",
    "        update_all_checkpoints()\n",
    "    \n",
    "    def get_pydantic_model() -> Checkpoints:\n",
    "        \"\"\"Converts the current editor state back to a Pydantic model\"\"\"\n",
    "        return Checkpoints(checkpoints=[\n",
    "            LearningCheckpoint(**checkpoint)\n",
    "            for checkpoint in checkpoints\n",
    "        ])\n",
    "    \n",
    "    # Create initial checkpoint widgets\n",
    "    checkpoints_widgets = [\n",
    "        create_checkpoint_widget(checkpoint, i)\n",
    "        for i, checkpoint in enumerate(checkpoints)\n",
    "    ]\n",
    "    \n",
    "    # Add new checkpoint button\n",
    "    add_checkpoint_btn = widgets.Button(\n",
    "        description='Add checkpoint',\n",
    "        button_style='success',\n",
    "        layout=widgets.Layout(margin='20px 0')\n",
    "    )\n",
    "    add_checkpoint_btn.on_click(add_new_checkpoint)\n",
    "    \n",
    "    # Main container\n",
    "    main_container = widgets.VBox(\n",
    "        checkpoints_widgets + [add_checkpoint_btn],\n",
    "        layout=widgets.Layout(\n",
    "            padding='20px',\n",
    "            border='1px solid #ddd',\n",
    "            border_radius='5px'\n",
    "        )\n",
    "    )\n",
    "    \n",
    "    def update_main_container():\n",
    "        \"\"\"Updates the main container\"\"\"\n",
    "        main_container.children = tuple(checkpoints_widgets + [add_checkpoint_btn])\n",
    "    \n",
    "    # Add method to container to retrieve data later\n",
    "    main_container.get_model = get_pydantic_model\n",
    "    \n",
    "    return main_container"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "95e22b2b-1ef0-40c4-b5d1-5e20055ae03a",
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoints = event['checkpoints']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "0fef90d9-fc86-47c8-845b-c81af14a66a6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "58101b36ed6f49099e8fc89db88d0449",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(VBox(children=(HBox(children=(HTML(value='<h3 style=\"margin: 0;\">Checkpoint 1</h3>'), Checkbox(…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "editor = create_checkpoint_editor(checkpoints)\n",
    "display(editor)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bebd2492",
   "metadata": {},
   "source": [
    "## Widget preview - Human in the loop checkpoints modifications\n",
    "\n",
    "![Chiron Widget](../images/chiron_widget.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "179a2b90-2ee2-43f7-b888-b4e1914798b9",
   "metadata": {},
   "source": [
    "## Upade state with adjusted checkpoints"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "186311ae-96e5-4164-8900-e6e6c05592ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "updated_model = editor.get_model()\n",
    "graph.update_state(thread, {\"checkpoints\": updated_model}, as_node=\"generate_checkpoints\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11661cd6-866b-4521-820b-ad8a013b9e38",
   "metadata": {},
   "source": [
    "## Run agent with input after printing question\n",
    "\n",
    "example answer:\n",
    "Anemia is a medical condition characterized by a deficiency in the number or quality of red blood cells (RBCs) or a reduced amount of hemoglobin in these cells, which impairs the blood's ability to carry oxygen to the body’s tissues. In clinical practice, anemia is significant because it can lead to symptoms such as fatigue, weakness, dizziness, and shortness of breath, which may severely impact quality of life and, in severe cases, lead to life-threatening complications.\n",
    "\n",
    "Types of Anemia and Their Causes\n",
    "Iron Deficiency Anemia: This is the most common type of anemia, often caused by insufficient iron intake, blood loss (e.g., heavy menstruation or gastrointestinal bleeding), or poor iron absorption (due to conditions like celiac disease). Iron is essential for hemoglobin production, and a deficiency in iron leads to decreased hemoglobin levels, reducing oxygen delivery to tissues.\n",
    "\n",
    "Vitamin B12 or Folate Deficiency Anemia: This type of anemia, sometimes called megaloblastic anemia, occurs due to a deficiency in vitamin B12, folate, or both. These vitamins are crucial for DNA synthesis in red blood cells. Causes include poor dietary intake, absorption issues (e.g., pernicious anemia, which is due to the loss of stomach cells producing intrinsic factor needed for B12 absorption), or conditions affecting the small intestine.\n",
    "\n",
    "Hemolytic Anemia: This type of anemia is caused by the premature destruction of red blood cells, which can occur due to autoimmune diseases, genetic conditions (e.g., sickle cell disease or hereditary spherocytosis), infections, or certain medications. In hemolytic anemia, the bone marrow cannot keep up with the rapid loss of RBCs, resulting in low RBC counts and reduced oxygen-carrying capacity.\n",
    "\n",
    "Role of Hemoglobin in Diagnosing Anemia\n",
    "Hemoglobin is a protein in red blood cells that binds to oxygen and transports it throughout the body. In clinical practice, hemoglobin levels are crucial for diagnosing anemia, as low levels indicate insufficient oxygen-carrying capacity. A Complete Blood Count (CBC) test measures hemoglobin levels and provides insights into red blood cell count and size, helping identify the type and severity of anemia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "5a8d1a64-fcf1-4d17-ac88-4650ea9a532b",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[32m2024-11-17 23:43:58 INFO semantic_chunkers.utils.logger Single document exceeds the maximum token limit of 512. Splitting to sentences before semantically merging.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c6006276b7bc459998470ead5e78abf8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/1 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Imagine you are explaining anemia to a friend who has no medical background. How would you define anemia and describe its general causes? Can you identify and explain the major types of anemia, such as iron deficiency, vitamin B12 deficiency, and hemolytic anemia? Additionally, use examples to illustrate how anemia physiologically affects the body, ensuring your explanation is clear and easy to understand.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Answer the question above:  Anemia is a medical condition characterized by a deficiency in the number or quality of red blood cells (RBCs) or a reduced amount of hemoglobin in these cells, which impairs the blood's ability to carry oxygen to the body’s tissues. In clinical practice, anemia is significant because it can lead to symptoms such as fatigue, weakness, dizziness, and shortness of breath, which may severely impact quality of life and, in severe cases, lead to life-threatening complications.  Types of Anemia and Their Causes Iron Deficiency Anemia: This is the most common type of anemia, often caused by insufficient iron intake, blood loss (e.g., heavy menstruation or gastrointestinal bleeding), or poor iron absorption (due to conditions like celiac disease). Iron is essential for hemoglobin production, and a deficiency in iron leads to decreased hemoglobin levels, reducing oxygen delivery to tissues.  Vitamin B12 or Folate Deficiency Anemia: This type of anemia, sometimes called megaloblastic anemia, occurs due to a deficiency in vitamin B12, folate, or both. These vitamins are crucial for DNA synthesis in red blood cells. Causes include poor dietary intake, absorption issues (e.g., pernicious anemia, which is due to the loss of stomach cells producing intrinsic factor needed for B12 absorption), or conditions affecting the small intestine.  Hemolytic Anemia: This type of anemia is caused by the premature destruction of red blood cells, which can occur due to autoimmune diseases, genetic conditions (e.g., sickle cell disease or hereditary spherocytosis), infections, or certain medications. In hemolytic anemia, the bone marrow cannot keep up with the rapid loss of RBCs, resulting in low RBC counts and reduced oxygen-carrying capacity.  Role of Hemoglobin in Diagnosing Anemia Hemoglobin is a protein in red blood cells that binds to oxygen and transports it throughout the body. In clinical practice, hemoglobin levels are crucial for diagnosing anemia, as low levels indicate insufficient oxygen-carrying capacity. A Complete Blood Count (CBC) test measures hemoglobin levels and provides insights into red blood cell count and size, helping identify the type and severity of anemia.\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    # Review\n",
    "    current_question = event.get('current_question', '')\n",
    "    if current_question:\n",
    "        print(current_question)\n",
    "\n",
    "answer_question = input(\"Answer the question above: \")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd42c4d8-77b4-43fc-a16f-e586f7d4d957",
   "metadata": {},
   "source": [
    "## Update state with answered question"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5a4f84f-2b69-4b17-8da8-592aa4d525fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "graph.update_state(thread, {\"current_answer\": answer_question}, as_node=\"user_answer\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08a007ab-bd9d-425a-a2fa-c5197a16a8dd",
   "metadata": {},
   "source": [
    "## Run Agent and print the verification or/and teaching results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "2a01ec2a-2a34-40cd-8766-70b61c55ca3c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('verify_answer',)\n",
      "('verify_answer',)\n",
      "('generate_question',)\n",
      "('user_answer',)\n",
      "\n",
      "==================================================\n",
      "              📊 VERIFICATION RESULTS              \n",
      "==================================================\n",
      "\n",
      "📈 Understanding Level: [██████████████████░░] 90.0%\n",
      "\n",
      "💡 Feedback:\n",
      "The student's answer provides a comprehensive and clear explanation of anemia, its causes, and its types. The definition of anemia is accurate, highlighting the deficiency in red blood cells or hemoglobin and its impact on oxygen transport. The major types of anemia are correctly identified and explained, including iron deficiency, vitamin B12 deficiency, and hemolytic anemia. The physiological effects of anemia, such as fatigue and weakness, are mentioned, although the examples could be more detailed. The answer aligns well with the success criteria, demonstrating a strong understanding of the topic.\n",
      "\n",
      "🎯 Suggestions:\n",
      "  1. Provide more detailed examples of how anemia affects the body physiologically.\n",
      "  2. Simplify the language further to ensure it is accessible to someone with no medical background.\n",
      "  3. Include more examples of symptoms or scenarios to illustrate the impact of anemia.\n",
      "\n",
      "🔍 Context Alignment:\n",
      "False\n",
      "\n",
      "--------------------------------------------------\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    print(graph.get_state(thread).next)\n",
    "    \n",
    "print_verification_results(event)\n",
    "print_teaching_results(event)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a25340c-1afb-462a-9c53-ba8f849de488",
   "metadata": {},
   "source": [
    "## Answer second question \n",
    "\n",
    "As we can see, we have additional context from the Tavily search, which we did not add at the initial context"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "a53b791c-33ec-44a6-85aa-d690cdafcbd9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['Context: LangGraph is a library within the LangChain ecosystem designed to tackle these challenges head-on. LangGraph provides a framework for defining, coordinating, and executing multiple LLM agents (or chains) in a structured manner. It simplifies the development process by enabling the creation of cyclical graphs, which are essential for developing\\n Source: https://www.datacamp.com/tutorial/langgraph-tutorial\\n',\n",
       " \"Context: Combining LangGraph with LangChain agents, agents can be both directed and cyclic. A Directed Acyclic Graph (DAG) is a type of graph used in computer science and mathematics. Here's a simple explanation: Directed: Each connection (or edge) between nodes (or vertices) has a direction, like a one-way street.\\n Source: https://cobusgreyling.substack.com/p/langgraph-from-langchain-explained\\n\",\n",
       " 'Context: LangGraph — Intuitively and Exhaustively Explained | by Daniel Warfield | Sep, 2024 | Towards Data Science LangGraph — Intuitively and Exhaustively Explained In this article we’ll explore “LangGraph”, a cutting-edge tool for making LLM agents that are actually useful. We’ll then discuss how LangGraph can be used to address these shortcomings to make more useful and maintainable agents. We’ll use this state graph to build an agent which is capable of performing a complex task which requires the agent to deal with natural conversation, hard rules, and application logic. By the end of this article you’ll understand why LangGraph exists, why it’s important, and how to use it within your own projects.\\n Source: https://towardsdatascience.com/langgraph-intuitively-and-exhaustively-explained-435ef706f0f9\\n']"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "context_store.get_context(event['context_key'])['chunks'][:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c1e8e8a-4331-4f3e-933e-79d33846afa1",
   "metadata": {},
   "source": [
    "## Take a look at the second question"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "ffcd417f-5ace-4e69-aa05-3a10289fb583",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Can you explain what Langgraph is and provide a clear definition of its basic concepts?\n"
     ]
    }
   ],
   "source": [
    "print(event['current_question'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "0ee88abd-93e3-44de-9c80-37ccecdf7ce4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Answer the second question  I don't know :(\n"
     ]
    }
   ],
   "source": [
    "answer_question = input(\"Answer the second question \")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "df600a63-08da-40eb-a4d8-e703aaf49870",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'configurable': {'thread_id': '20',\n",
       "  'checkpoint_ns': '',\n",
       "  'checkpoint_id': '1efa5365-b346-6432-800b-085763bc9a0e'}}"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.update_state(thread, {\"current_answer\": answer_question}, as_node=\"user_answer\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0aa5cd6-1682-4863-b8ee-0f80cf4e2f85",
   "metadata": {},
   "source": [
    "## Run agent, see how he treated our lack of knowledge"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "a34d6c60-0d57-41a3-93a3-e068c6933b6d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('verify_answer',)\n",
      "('verify_answer',)\n",
      "()\n",
      "\n",
      "==================================================\n",
      "              📊 VERIFICATION RESULTS              \n",
      "==================================================\n",
      "\n",
      "📈 Understanding Level: [░░░░░░░░░░░░░░░░░░░░] 0.0%\n",
      "\n",
      "💡 Feedback:\n",
      "The student's answer does not demonstrate any understanding of Langgraph or its basic concepts. The response 'I don't know' indicates a lack of engagement with the material and an inability to provide any information related to the question or the context provided.\n",
      "\n",
      "🎯 Suggestions:\n",
      "  1. Review the context provided about Langgraph to understand its purpose and functionality.\n",
      "  2. Learn the key features of Langgraph, such as its ability to handle cycles, controllability, and persistence.\n",
      "  3. Understand how Langgraph differentiates itself from other LLM frameworks, particularly in its use of cyclical graphs.\n",
      "  4. Familiarize with the LangChain ecosystem and how Langgraph fits within it.\n",
      "\n",
      "🔍 Context Alignment:\n",
      "False\n",
      "\n",
      "--------------------------------------------------\n",
      "\n",
      "\n",
      "======================================================================\n",
      "                    🎓 FEYNMAN TEACHING EXPLANATION                    \n",
      "======================================================================\n",
      "\n",
      "📚 SIMPLIFIED EXPLANATION:\n",
      "──────────────────────────────\n",
      "LangGraph is like a toolkit for building smart digital\n",
      "helpers that can talk and work together. Imagine you have a\n",
      "group of robots, each with a different job, like one for\n",
      "answering questions, another for finding information, and\n",
      "another for making decisions. LangGraph helps you organize\n",
      "these robots so they can work together smoothly, even if\n",
      "they need to go back and forth to complete a task. It's part\n",
      "of a bigger system called LangChain, which is like a big\n",
      "toolbox for building these kinds of helpers. LangGraph is\n",
      "special because it lets these robots work in loops, which\n",
      "means they can repeat steps if needed, making them more\n",
      "flexible and efficient.\n",
      "\n",
      "💡 KEY CONCEPTS:\n",
      "──────────────────────────────\n",
      "  1. LangGraph is a framework for organizing and managing multiple LLM agents.\n",
      "  2. It allows for the creation of cyclical graphs, enabling agents to repeat tasks as needed.\n",
      "  3. LangGraph is part of the LangChain ecosystem, which provides tools for building intelligent agents.\n",
      "  4. It helps in coordinating complex workflows by allowing agents to communicate and work together.\n",
      "\n",
      "🔄 ANALOGIES & EXAMPLES:\n",
      "──────────────────────────────\n",
      "  1. Think of LangGraph as a conductor in an orchestra, ensuring all musicians (agents) play in harmony, even if they need to repeat parts of the music.\n",
      "  2. Imagine LangGraph as a project manager who organizes a team (agents) to work on a project, allowing them to revisit tasks if necessary to ensure everything is done correctly.\n",
      "  3. LangGraph is like a traffic controller, directing the flow of information between different agents, ensuring they can loop back if they miss something.\n",
      "\n",
      "======================================================================\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    print(graph.get_state(thread).next)\n",
    "    \n",
    "print_verification_results(event)\n",
    "print_teaching_results(event)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20755b0c-7da0-42c0-b3bb-993cb4bcd728",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
